using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;

namespace TfsSpam.Differ
{
    /// <summary>
    /// The FileDiffer class provides support for comparing two
    /// files and looking up the differences between them.
    /// </summary>
    internal class FileDiffer
    {
        #region -- Private class members
        /// <summary>
        /// The fully qualified path of the old file.
        /// </summary>
        private readonly string _oldFilePath;

        /// <summary>
        /// The fully qualified path of the new file.
        /// </summary>
        private readonly string _newFilePath;

        /// <summary>
        /// The number of lines of context to retrieve for
        /// each difference in the files.
        /// </summary>
        private readonly uint _contextLines;

        /// <summary>
        /// The maximum number of lines to include from the
        /// files being compared.
        /// </summary>
        private readonly uint _maxIncludedLines;

        /// <summary>
        /// The list of prefixes that should be recognized for code
        /// highlighting processing.
        /// </summary>
        private readonly List<string> _highlightPrefixes;
        #endregion

        #region -- Public class constants
        /// <summary>
        /// The default maximum allowed file size for the files that
        /// will be compared (in bytes).
        /// </summary>
        public const uint DefaultMaxIncludedLines = 1000;

        /// <summary>
        /// The default number of lines of context to provide around
        /// each difference in the files.
        /// </summary>
        public const uint DefaultContextLines = 3;
        #endregion

        #region -- Constructor
        /// <summary>
        /// This overloaded version of the constructor allows the user to specify
        /// a maximum allowed size for the files to compare and the number of context
        /// lines to include for each difference in the files.
        /// </summary>
        /// <param name="oldFilePath">The fully qualified path of the old file.</param>
        /// <param name="newFilePath">The fully qualified path of the new file.</param>
        /// <param name="maxIncludedLines">The number of lines to include from the files being compared.</param>
        /// <param name="contextLines">The number of context lines to provide for each difference in the files.</param>
        /// <param name="highlightPrefixes">The list of prefixes recognized for code highlighting.</param>
        /// <exception cref="ArgumentException">
        /// Thrown if either file is larger than <see cref="FileDiffer.DefaultMaxSize"/> bytes.
        /// </exception>
        public FileDiffer( string oldFilePath, string newFilePath, uint maxIncludedLines, uint contextLines, List<string> highlightPrefixes )
        {
            _oldFilePath = oldFilePath;
            _newFilePath = newFilePath;
            _contextLines = contextLines;
            _maxIncludedLines = maxIncludedLines;
            _highlightPrefixes = highlightPrefixes;
        }
        #endregion

        #region -- Public methods
        /// <summary>
        /// Generates the information about the differences between the two files.
        /// </summary>
        /// <remarks>
        /// The difference information is not cached, it is generated every time
        /// this method is called.
        /// </remarks>
        /// <returns>The differences between the two files.</returns>
        public FileDifferenceResult GetFileDifferences()
        {
            string[] oldFileLines;
            string[] newFileLines;
            Diff.Item[] fileDifferences = CompareFiles( out oldFileLines, out newFileLines );
            List<FileDifference> differences = new List<FileDifference>();
            uint linesIncluded = 0;
            bool truncated = false;

            foreach ( Diff.Item diffItem in fileDifferences )
            {
                FileDifference difference = new FileDifference( diffItem, oldFileLines, newFileLines, _contextLines, _highlightPrefixes );

                differences.Add( difference );
                linesIncluded += GetDifferenceLines( difference );
                if ( linesIncluded > _maxIncludedLines && _maxIncludedLines > 0 )
                {
                    // We've reached the limit for how many lines of text
                    // to include from the files being compared.
                    truncated = true;
                    break;
                }
            }

            return new FileDifferenceResult( differences, truncated );
        }
        #endregion

        #region -- Line counting support
        /// <summary>
        /// Returns the number of lines included from the files by
        /// the specified <see cref="FileDifference"/>.
        /// </summary>
        /// <param name="difference">The <see cref="FileDifference"/> object whose lines should be counted.</param>
        /// <returns>The number of lines included from the files being compared by the difference.</returns>
        static private uint GetDifferenceLines( FileDifference difference )
        {
            return (uint) difference.Lines.Count;
        }
        #endregion

        #region -- Private file comparison methods
        /// <summary>
        /// Compares the two files and returns the differences between them.
        /// </summary>
        /// <param name="oldFileLines">The return parameter for the lines of text in the old file.</param>
        /// <param name="newFileLines">The return parameter for the lines of text in the new file.</param>
        /// <returns>The differences between the two files.</returns>
        private Diff.Item[] CompareFiles( out string[] oldFileLines, out string[] newFileLines )
        {
            string oldFileContent;
            string newFileContent;
            Diff diff = new Diff();

            // Get the contents of the old and new file...
            GetFileContent( _oldFilePath, out oldFileContent, out oldFileLines );
            GetFileContent( _newFilePath, out newFileContent, out newFileLines );

            // ...compare them.
            return diff.DiffText( oldFileContent, newFileContent );
        }

        /// <summary>
        /// Retrieves the contents of a text file both as a string and broken down
        /// into individual lines of text.
        /// </summary>
        /// <param name="fileName">The fully qualified path of the file that should be read.</param>
        /// <param name="fileContent">The contents of the file as a contigious string.</param>
        /// <param name="fileLines">The contents of the file broken down into individual lines.</param>
        static private void GetFileContent( string fileName, out string fileContent, out string[] fileLines )
        {
            // Get the complete contents of the file.
            fileContent = File.ReadAllText( fileName );

            // Change all \r\n character sequences to a simple \n so that we
            // can break the file content into separate lines.
            fileLines = fileContent.Replace( "\r\n", "\n" ).Split( '\n' );
        }
        #endregion
    }
}
